import pygame   # 导入 pygame 库，这是制作游戏的基础
import random   # 导入 random 库，用于生成随机数，比如 AI 在没有目标时随机移动
import math     # 导入 math 库，用于进行数学计算，比如计算两点之间的直线距离

# 定义 WhaleLogo 类
class WhaleLogo:
    # 初始化方法
    def __init__(self, screen_width, screen_height, name="", student_id=""):
        self.screen_width = screen_width
        self.screen_height = screen_height

        # 定义鲸鱼绘制区域的尺寸和移动速度
        self.width = 280
        self.height = 100
        self.speed = pygame.Vector2(1.2, 0.6)
        # 定义鲸鱼绘制区域的尺寸和移动速度

        # 创建一个临时的、透明的画布，所有鲸鱼的绘制都在这个画布上完成，最后再整体旋转并贴到主屏幕
        self.whale_surface = pygame.Surface((self.width + 120, self.height + 100), pygame.SRCALPHA)
        """
        pygame.Surface(...),这个函数的功能是：“凭空创建一块全新的、空白的画布”。
        1.(self.width + 120, self.height + 100)：这是第一个参数，设置这块新画布的尺寸。
        它的宽度比鲸鱼的 width 还宽 120 像素，高度也多了 100 像素。这样做是为了预留出足够的空白空间（比如用来画鲸鱼喷出的水花或者旋转时的空白）
        2.pygame.SRCALPHA：。它的意思是告诉 Pygame：“请把这块新画布设置为支持透明度的模式。”（就像一张透明的塑料片，而不是一张白纸）。
        """
        # 创建一个临时的、透明的画布，所有鲸鱼的绘制都在这个画布上完成，最后再整体旋转并贴到主屏幕
        self.rect = self.whale_surface.get_rect()
        """
        1.get_rect() 的意思是获取矩形
        2.“创建并返回一个看不见的、与这张画布（Surface）大小完全相同的 pygame.Rect 对象。”
        
        """
        # 动画主计时器
        self.time = 0
        """
        self.time 变量的功能是：“用来记录时间（或帧数），以便控制动画的播放。
        """

        # 定义鲸鱼身体各部分的颜色
        self.color_body = (25, 80, 180)     # 主体
        self.color_belly = (110, 180, 230)      # 肚皮
        self.color_highlight = (160, 210, 255)  # 高光
        self.color_pleats = (90, 160, 220)  # 腹部褶皱

        # 环绕鲸鱼的泡泡
        self.bubbles = []
         #(173, 216, 230): 一种淡蓝色 (LightBlue)
         #(135, 206, 250): 一种亮天蓝色 (LightSkyBlue)
        # (224, 255, 255): 一种非常浅的青色 (LightCyan)
        #列表可变，元组不可变
        self.palette_bubbles = [(173, 216, 230), (135, 206, 250), (224, 255, 255)]
        for _ in range(30):
            self.bubbles.append({
                'orbit_radius': random.uniform(80, 180),# 环绕半径
                'angle': random.uniform(0, 2 * math.pi),# 初始角度
                'speed': random.uniform(0.01, 0.02) * random.choice([-1, 1]),# 旋转速度
                'radius': random.uniform(1, 6), # 泡泡大小
                'color': random.choice(self.palette_bubbles),
                'alpha_phase': random.uniform(0, 2 * math.pi)   # 用于透明度变化的相位
            })

        ## --- 修改点 1：新增鲸鱼后方追踪泡泡的列表和颜色 --- ##
        self.trail_bubbles = [] # 存储追踪泡泡的列表
        # 新增一个更深一些的蓝色泡泡调色板，以示区别和层次感
        self.palette_trail_bubbles = [(90, 160, 220), (120, 190, 240), (70, 130, 190)]
        # 创建字体和文字图像
        self.font = pygame.font.SysFont("Simsun", 16, bold=True)
        self.name_surf = self.font.render(f"姓名: {name}", True, (255, 255, 255))
        self.id_surf = self.font.render(f"学号: {student_id}", True, (255, 255, 255))

        # 初始化位置
        self.pos = pygame.Vector2(0, 0)
        """
        1. Vector2 帮你内置了所有你需要的数学运算
        self.pos = pygame.Vector2(0, 0) 你想让它移动，你只需要准备另一个“速度向量” speed = pygame.Vector2(5, 10)。
        你可以直接把两个向量相加：self.pos = self.pos + speed (或者 self.pos += speed) Pygame 会自动帮你完成 (0+5, 0+10) 的计算，代码变得极其简洁和易读。
        2.Vector2 核心功能:向量加减法： pos1 + pos2 (两个位置相加) pos - speed (位置减去速度)
        与数字的乘除法： speed * 2 (速度加倍) direction * 100 (沿着方向移动 100 像素)
        计算长度/大小 (Magnitude)： pos.length() (计算这个点离原点 (0,0) 有多远)
        计算两点间距离： pos1.distance_to(pos2) (计算 pos1 到 pos2 的直线距离)
        归一化 (Normalize)： direction.normalize() (计算“单位向量”，即保持方向不变，但长度变为 1。这在计算移动方向时超级有用)
        旋转 (Rotate)： direction.rotate(90) (将这个向量旋转 90 度)
        """
        self.reset()
        """
        调用  reset 方法
        """
        # <--- 【新增代码 1/5】 --->
        # 新增：控制眼睛闭合的状态和计时器
        self.eye_closed = False  # 眼睛是否闭合的标志
        self.eye_closed_timer = 0  # 闭合状态的剩余时间 (单位：帧)
        self.eye_collision_rect = None  # 用于存储眼睛碰撞区域的 Rect 对象
        # <--- 【新增代码 1/5】结束 --->

    # 重置方法，当鲸鱼移出屏幕时调用
    def reset(self):
        self.pos = pygame.Vector2(-self.width - 50, -self.height - 50)
        self.rect.topleft = self.pos
        """
        self.pos (逻辑位置)和self.rect (物理位置 / 绘图位置)一致，同步，
        刚刚用 self.pos 计算出了鲸鱼‘应该’在的新位置（那个隐藏坐标）。现在，请立刻把这个新位置，更新（赋值）给 self.rect 的左上角（topleft），
        这样 Pygame 的绘图系统在下一帧画画时，才能知道这个‘物理’上的新位置
        """
        self.trail_bubbles.clear()  # 重置时清空追踪泡泡,清空追踪泡泡
        """
        .clear() 是 Python 列表 (List) 自带的一个标准方法,它的功能非常简单：清空这个列表里的所有元素
        """
        # <--- 【新增代码 2/5】 --->
        # 重置时也确保眼睛是睁开的
        self.eye_closed = False
        self.eye_closed_timer = 0
        self.eye_collision_rect = None  # 清空碰撞区域
        # <--- 【新增代码 2/5】结束 --->

    # 更新方法，每一帧调用
    def update(self):
        # 更新鲸鱼的位置
        self.pos += self.speed
        # 额外增加一个sin函数驱动的上下浮动，模拟游动
        self.pos.y += math.sin(self.time * 0.03) * 0.5
        """
        1.用math.sin,sin（正弦函数）是数学中一个神奇的函数，它的输出值永远在 -1.0 到 1.0 之间周期性地摆动，就像一个波浪。
        这个特性完美地模拟了“上下浮动”这种周期性的动作。
        2.self.time 是一个每一帧都在增加的计时器
        把不断变化的 self.time 放进 sin 函数，sin 函数的输出值就会随着时间，平滑地从 1 变到 -1，再从 -1 变回 1，周而复始。
        * 0.03 是为了减慢这个浮动的速度。如果没有它，鲸鱼会上下抖动得非常快。
        3.* 0.5  这是为了减小这个浮动的幅度。 
        sin 函数的输出范围是 [-1.0, 1.0]。如果不乘，鲸鱼每帧最多会上下移动 1 个像素.
         把它乘以 0.5 之后，输出的范围就变成了 [-0.5, 0.5]。
         这意味着鲸鱼每次上下浮动的最大距离只有 0.5 像素，动作看起来会非常细腻、轻微，更像是真实的游动，而不是剧烈的跳动。
        """

        self.rect.topleft = round(self.pos.x), round(self.pos.y)
        """
        round() 是 Python 自带的一个内置函数。
        意思：“四舍五入”
        功能：把一个小数（浮点数）转换成离它最近的整数。
        round(100.2) 会得到 100。
        round(100.8) 会得到 101。
        1. self.pos 是一个 Vector2，因为有 sin(...) * 0.5 这样的计算，它的 x 和 y 值很可能是小数，比如 (150.35, 200.81)。
        但是 Pygame 在画图或设置 rect（矩形）位置时，它只接受整数。如果你给它一个小数，可能会报错或出现像素抖动。
        所以 round(self.pos.x), round(self.pos.y) 是一个“安全转换”操作，确保把计算出的精确小数位置，
        转换成 Pygame 能理解的整数像素位置。
        """
        # 计时器递增
        self.time += 1

        # 更新环绕泡泡,更新环绕泡泡的角度和透明度相位
        for bubble in self.bubbles:
            bubble['angle'] += bubble['speed']
            bubble['alpha_phase'] += 0.05


        #修改点 2：更新和生成鲸鱼后方追踪泡泡
        # 每隔一段时间生成新的追踪泡泡,每10帧生成一个新的追踪泡泡
        if self.time % 10 == 0:
            # 泡泡从鲸鱼尾部附近产生,计算泡泡的出生点，大约在鲸鱼尾部
            """
            1.math.radians(-45):意思是把 -45 度 转换成数学计算（sin/cos）能看懂的“弧度”。
            2.math.cos(...):意思是计算这个 -45 度角的余弦值。
            3.(增加抖动): + random.uniform(-10, 10)意思: 用来增加自然感.
            功能: random.uniform(-10, 10) 会返回一个 -10 到 +10 之间的随机小数（比如 5.2, -8.1）。
            目的: 把这个随机数加到我们刚刚算好的“精确”坐标上。
            效果: 这会让泡泡的出生点不是永远在同一个“死”点上，而是在一个 20 像素宽的范围内轻微地左右抖动。这让气泡看起来更自然、生动。
            4.... * ... * ... (三者相乘):(self.width * 0.5) * (math.cos(...))
            意思是“距离”乘以“水平比例”，得到了一个总的“水平偏移量”。
            5.X 坐标减小，就意味着“向左移动”,+ ... sin(...)，sin(-45) 是负数，所以 Y 坐标也是减小，代表“向上移动”。
            因此 -45 度角配合 X- 和 Y+，最终定位到了鲸鱼的左上方，这在游戏里通常是“左后方”或“尾部”的方向。)
            """

            tail_x = self.rect.centerx - self.width * 0.5 * math.cos(math.radians(-45)) + random.uniform(-10, 10)
            tail_y = self.rect.centery + self.height * 0.5 * math.sin(math.radians(-45)) + random.uniform(-10, 10)
            self.trail_bubbles.append({
                'pos': pygame.Vector2(tail_x, tail_y),  # 位置，
                'radius': random.uniform(2, 8), # 大小
                'color': random.choice(self.palette_trail_bubbles),
                'alpha': 200,   # 初始透明度
                'speed': pygame.Vector2(random.uniform(-0.5, 0.5), random.uniform(-1, -0.5)),  # 向上飘,向上漂浮的速度
                'life': 120  # 泡泡生命周期
            })
            """ 把刚刚计算出来的 tail_x (X坐标) 和 tail_y (Y坐标) 拿过来。
             用 pygame.Vector2() 把它们“打包”成一个“位置向量”。把这个“位置向量”存到新泡泡字典的 'pos' (位置) 键里面"""

        # 更新追踪泡泡的位置和透明度
        for bubble in self.trail_bubbles:
            bubble['pos'] += bubble['speed']
            bubble['alpha'] = max(0, bubble['alpha'] - 2)  # 逐渐变淡
            bubble['radius'] = max(0.5, bubble['radius'] - 0.05)  # 逐渐变小
            bubble['life'] -= 1

        # 移除生命周期结束的泡泡,使用列表推导式，高效地移除所有死掉的泡泡
        self.trail_bubbles = [b for b in self.trail_bubbles if b['life'] > 0 and b['alpha'] > 0 and b['radius'] > 0]

        # <--- 【新增代码 3/5】 --->
        # 更新眼睛闭合计时器
        if self.eye_closed:
            self.eye_closed_timer -= 1  # 时间递减
            if self.eye_closed_timer <= 0:
                self.eye_closed = False  # 时间到了，重新睁开眼睛
        # <--- 【新增代码 3/5】结束 --->


        # 如果鲸鱼完全移出屏幕，则重置
        if self.rect.left > self.screen_width + 50 or self.rect.top > self.screen_height + 50:
            self.reset()

    # 内部方法，专门负责程序化绘制鲸鱼身体
    def _draw_whale_body(self, surface, rect, time):
        # ... (这是一个非常复杂的程序化绘图过程，核心思想如下)
        # 1. 它通过一个 for 循环，使用 sin 和 cos 函数计算出鲸鱼背部和腹部的一系列坐标点。
        # 2. 它将 `time` 变量引入计算，用来控制嘴巴的张合 (mouth_open_factor)。
        # 3. 它将这些点连接起来，用 pygame.draw.polygon 绘制出鲸鱼的基本形状。
        # 4. 它通过在基本形状上叠加半透明的、稍作偏移的多边形，来绘制肚皮和高光，增加立体感。
        # 5. 它还循环绘制多条曲线，来模拟腹部的褶皱。
        # 6. 最后，它还单独绘制了背鳍。
        # (由于其内部数学公式非常复杂，逐行解释意义不大，关键是理解其整体思路)
        center_x, center_y = rect.center
        width, height = rect.width, rect.height

        mouth_open_factor = (math.sin(time * 0.07) + 1) / 2
        """
        1.math.sin(time * 0.07)：根据 time（计时器）生成一个在 -1 到 +1 之间平滑摆动的波浪。
        2. ... + 1：让这个波浪在 0 到 2 之间摆动。
        3.... / 2：让这个波浪在 0 到 1 之间摆动。
        结果：mouth_open_factor 是一个百分比（从 0.0 到 1.0），它代表了“这一帧，鲸鱼的嘴巴应该张开百分之多少”。
        """

        points_top, points_bottom = [], []
        num_segments = 40
        for i in range(num_segments + 1):
            angle = (i / num_segments) * math.pi
            dx = (width / 2) * math.cos(angle)
            dy_top = -(height / 2.0) * math.sin(angle) ** 0.8
            base_dy_bottom = (height / 1.7) * math.sin(angle) ** 0.9

            jaw_open_offset = 0
            if i > num_segments * 0.6:
                open_progress = (i - num_segments * 0.6) / (num_segments * 0.4)
                """
                这个 if 语句的意思是：“只有当循环 i 超过 24（也就是进入了鲸鱼的后 40% 部分，即‘下巴’区域）时，才执行下面的代码
                open_progress = (i - num_segments * 0.6) / (num_segments * 0.4) 是计算进度的
                1.分母: num_segments * 0.4  40 * 0.4 = 16。 这代表了“下巴”区域的总长度（从第 24 节到第 40 节，总共 16 节）。
                分子 (Numerator): i - num_segments * 0.6    num_segments * 0.6 是 24（“下巴”的起始点）。 i 是当前点。
                这个表达式的意思是：“我们现在在‘下巴’区域里，前进了多少节
                2.. 把分子和分母合起来看：open_progress = (当前在下巴前进了多少) / (下巴的总长度)
                当 i = 24 (刚开始) 时：open_progress = 0 / 16 = 0.0 (进度 0%)
                当 i = 32 (正中间) 时：open_progress = 8 / 16 = 0.5 (进度 50%)
                当 i = 40 (已结束) 时：open_progress = 16 / 16 = 1.0 (进度 100%)
                """
                jaw_open_offset = mouth_open_factor * 12 * math.sin(open_progress * math.pi / 2)
                """
                下巴偏移量 = (当前嘴巴张开了百分之多少) * (最多能张开12像素) * (当前这个点应该达到最大张开幅度的百分之多少)
                1.计算出在鲸鱼下巴的某个特定点上，嘴巴张开时，这个点应该向下移动多少像素。
                这个计算结果被存到 jaw_open_offset（下巴张开偏移量）这个变量里
                2.jaw_open_offset = (动画总开关) * (最大能张多开) * (当前这个点的形状比例)
                ①mouth_open_factor (动画总开关)   作用: 这是“动画的总控制器”。
                值: 它是一个从 0.0 (嘴巴完全闭合) 到 1.0 (嘴巴张到最大) 的小数。
                影响: 如果这一帧鲸鱼的嘴巴是闭着的 (mouth_open_factor = 0)，那么整个公式的结果都会是 0，下巴就完全不会有任何偏移。
                如果嘴巴是半张的 (0.5)，那么总的偏移量也会减半。
                ②12 (最大能张多开)    作用: 这是一个“基准幅度”，单位是像素。   值: 就是数字 12。
                影响: 它定义了鲸鱼嘴巴张到最大时，下巴最深处最多可以向下移动 12 个像素。
                你可以把这个数字改大（比如 20），鲸鱼的嘴巴就会张得更开；改小（比如 5），嘴巴张开的幅度就变小了
                ③math.sin(open_progress * math.pi / 2) (当前这个点的形状比例)
                作用: 这是最巧妙的部分，它是一个**“形状控制器”，用来模拟下巴张开时的弧线**，确保张开的动作不是僵硬的。
                值: 它的计算结果也是一个从 0.0 到 1.0 的小数。
                影响:open_progress: 这是我们上一步算出来的“下巴绘制进度”（从0.0到1.0）。
                ... * math.pi / 2: 把它转换成一个从 0 度到 90 度的角度（用弧度表示）。
                math.sin(...): 计算这个角度的正弦值。当角度从 0 变到 90 度时，sin 的值会从 0 平滑地、非线性地增加到 1。
            它意味着：
                在下巴的起始点（靠近嘴巴连接处，open_progress ≈ 0），这个比例因子几乎为 0，所以张开的偏移量也几乎为 0。
                在下巴的最末端（下巴尖，open_progress = 1），这个比例因子为 1，所以张开的偏移量达到最大。
                这完美地模拟了下巴绕着一个轴转动的弧线效果。
                """

            dy_bottom = base_dy_bottom + jaw_open_offset
            """
            把鲸鱼‘默认的腹部曲线位置’ (base_dy_bottom) 和‘因张嘴而产生的额外向下位移’ (jaw_open_offset) 加在一起，
            得到这个点在这一帧的‘最终 Y 坐标’ (dy_bottom)
            """
            points_top.append((center_x + dx, center_y + dy_top))
            """将算好的背部节点存到列表中"""
            points_bottom.append((center_x + dx, center_y + dy_bottom))
            #将算好的腹部节点存到列表中
        """
        1.angle = ... * math.pi：它在计算一个从 0 度扫到 180 度的角度。
        2.dx = ... * math.cos(angle)：用 cos 计算这个点在水平方向的位置（cos 使其在身体中部宽，在头尾两端窄）。
        3.dy_top = ... * math.sin(angle) ** 0.8：这是鲸鱼背部的“魔法公式”。它用 sin 画出一条从 0 开始，向上拱起（因为是 -），
        再回到 0 的平滑曲线。** 0.8 是为了让曲线更平坦。
        base_dy_bottom = ...：这是鲸鱼腹部的“魔法公式”，画出一条向下拱起的曲线。
        4.jaw_open_offset = ...：这是动画！ 如果循环 i 超过了 60%（即画到了鲸鱼的“下巴”部分），
        它就会根据我们第2步算好的 mouth_open_factor，把腹部的点再往下移动一点。
        dy_bottom = base_dy_bottom + jaw_open_offset：腹部的最终 Y 坐标 = 基础曲线 + 嘴巴张开的偏移量。
        结果：循环结束后，points_top 里存了 41 个“背部”的点，points_bottom 里存了 41 个“腹部”（带动画）的点。
        """
        full_body_points = points_top + points_bottom[::-1]
        """
        1.points_bottom[::-1] (反转腹部曲线)
        points_bottom：这是你在 for 循环中建立的列表，里面装着构成鲸鱼腹部的所有坐标点。
        这些点的顺序是从鲸鱼的一端（比如头部）到另一端（比如尾部）。
    [::-1]：这是 Python 中一个非常酷的“切片”语法，它是“反转列表”的快捷方式
        2.points_top + ... (缝合两条曲线) points_top：这是鲸鱼背部的点列表（顺序是“从头到尾”）。
    +：当用在两个列表之间时，+ 号不是数学加法，而是**“列表串接” (Concatenation)** 的意思。它会把两个列表首尾相连，变成一个更长的列表。
        3.最终得到的 full_body_points 是一个包含了所有轮廓点的、单一的、连续的列表，它的路径是： [头, 背中点, 尾, 尾, 腹中点, 头]
        """

        pygame.draw.polygon(surface, self.color_body, full_body_points)
        """
        1.画出鲸鱼的实心身体轮廓   pygame.draw.polygon: 这是 Pygame 的“画多边形”工具
        surface: 画在哪里   画在 _draw_whale_body 函数传进来的那块“鲸鱼主画布”上
        self.color_body: 用什么颜色     用鲸鱼的“身体底色”
        full_body_points: 画什么形状     用你之前（用 points_top + points_bottom[::-1]）刚刚“缝合”好的一长串“完整的身体轮廓点”列表。
        (缺少 width 参数): 因为没有第 4 个 width 参数，polygon（多边形）默认会被“填充满”fill）。
        """
        temp_layer = pygame.Surface(surface.get_size(), pygame.SRCALPHA)
        """
        1.pygame.Surface(...): 这是一个核心功能，意思是“创建一块全新的空白画布”。
        surface.get_size(): 新画布有多大？surface.get_size() 会获取我们上一行刚画过的“鲸鱼主画布”的尺寸（比如 (200, 150)）。
        这保证了 temp_layer 这个新图层和我们的鲸鱼画布尺寸一模一样。
        2.pygame.SRCALPHA,这是一个“标记”，意思是“支持透明度”（Source Alpha）
        """
        highlight_points = []
        #目的是画高光，从背部往下 20% 开始的“高光带”。
        for pt in points_top: highlight_points.append((pt[0], pt[1] + height * 0.2))
        """
        1.这个循环在遍历构成鲸鱼背部的所有点 (points_top) pt：代表原始背部上的一个点，它的坐标是 (pt[0], pt[1])，也就是 (x, y)
        2.highlight_points.append(...)：它在添加一个新点。
        关键：新点的坐标是 (pt[0], pt[1] + height * 0.2)
        pt[0]：X 坐标不变。   pt[1] + height * 0.2：Y 坐标被改变了！它在原始 Y 坐标的基础上，加上了鲸鱼总高度的 20%。
        在 Pygame 里，Y 坐标增加意味着“向下移动”。
    结果：这一步描出了一条新的曲线，这条曲线的形状和鲸鱼的背部一样，但整体向下平移了一段距离。
        """
        for pt in points_bottom[::-1]: highlight_points.append((pt[0], pt[1] - height * 0.3))
        """
        1.这个循环在遍历构成鲸鱼腹部的所有点，并且是反转顺序的 ([::-1])，以保证描图的路径是连续的
        pt：代表原始腹部上的一个点 (x, y)。  关键：新点的坐标是 (pt[0], pt[1] - height * 0.3)
        pt[0]：X 坐标不变。   pt[1] - height * 0.3：Y 坐标被改变了！它在原始 Y 坐标的基础上，减去了鲸鱼总高度的 30%。
        在 Pygame 里，Y 坐标减小意味着“向上移动”。
    结果：这一步描出了一条新的曲线，这条曲线的形状和鲸鱼的腹部一样，但整体向上平移了一段距离。
        """
        pygame.draw.polygon(temp_layer, (*self.color_highlight, 80), highlight_points)
        """
        1.highlight_points 列表现在装满了我们刚刚描好的、完整的、封闭的新轮廓点（上半部分向下移，下半部分向上移
        2.pygame.draw.polygon：用“画多边形”工具。
        3.temp_layer：把这个形状画在我们之前创建的“透明图层”上。
        4.(*self.color_highlight, 80)：用“高光颜色”，并且加上 80 的透明度（0=全透明, 255=不透明），所以它是半透明的。
        5.highlight_points：告诉 Pygame 要画的形状。
        """
        #目的是画肚皮，从背部往下 25% 开始的“肚皮带
        belly_points = [(p[0], p[1] + height * 0.25) for p in points_top] + points_bottom[::-1]
        """
        1.列表推导式,for p in points_top: 它在遍历构成鲸鱼背部的所有点。p 就是其中一个点 (x, y)
        p[0]: 新点的 X 坐标保持不变 p[1] + height * 0.25: 新点的 Y 坐标被改变,它在原始 Y 坐标的基础上，加上了鲸鱼总高度的 25%。
        在 Pygame 里，Y 坐标增加意味着“向下移动”。
    结果是: 这一部分代码，生成了一个新的点列表。这个列表里的点构成了一条新的曲线，这条曲线的形状和鲸鱼的背部完全一样，但整体向下移动了一段距离。
        """
        pygame.draw.polygon(temp_layer, (*self.color_belly, 120), belly_points)
        #同理如上
        surface.blit(temp_layer, (0, 0))

        # 外层循环：用来画 8 条褶皱
        for i in range(8):
            #创建一个“空白的列表”
            pleat_points = []
            #计算这条褶皱的“基础 Y 坐标”
            y_offset = height * 0.22 + i * 5
            # 内层循环：用来画“一条”褶皱线上的所有“点”
            for j in range(int(num_segments * 0.6), int(num_segments * 0.95)):
                angle = (j / num_segments) * math.pi
                #根据 `j` 点的位置，计算出它在鲸鱼身体曲线上的“角度”。
                dx = (width / 2) * math.cos(angle)
                #用 `cos` 计算出这个点对应的“X 坐标偏移量”
                open_progress = (j - num_segments * 0.6) / (num_segments * 0.4)
                #计算“下巴张开”的进度百分比（0.0 到 1.0）
                jaw_open_offset = mouth_open_factor * 12 * math.sin(open_progress * math.pi / 2)
                #计算在“这一帧”，因为嘴巴张开，这个点需要“向下移动多少像素”。

                dy = (y_offset * math.sin(angle) ** 0.9) + jaw_open_offset
                #“最终 Y 坐标”，`(y_offset * math.sin(angle) ** 0.9)`：这是褶皱的“基础形状”。它用 `sin` 来画一条贴合肚皮的曲线。
                # `+ jaw_open_offset`：这是“加上动画”！把“基础形状”和“张嘴的偏移量”加在一起。
                # 结果：这条线不仅是弯的，而且还会随着嘴巴张合而上下移动！
                pleat_points.append((center_x + dx, center_y + dy))
                #把我们刚刚算好的这个点的“最终坐标” (x, y)，“添加” (append) 到 `pleat_points` 这个列表中去
            # 内层循环结束，回到外层循环
            if len(pleat_points) > 1:
                # 解释：这是一个“安全检查”。
                # `len(pleat_points)` 是“列表里有多少个点”。
                # 意思是：“我们至少需要 2 个点才能画一条线。检查一下，我们是不是真的算出了点？”
                pygame.draw.aalines(surface, (*self.color_pleats, 100), False, pleat_points)
                # 解释：这是“画线”的命令！
                # `pygame.draw.aalines`：这是一个特殊的画线工具。`aa` 的意思是“**抗锯齿**”(Anti-Aliased)，它画出的线**更平滑、更漂亮**。
                # `surface`：画在我们的“鲸鱼画布”上。
                # `(*self.color_pleats, 100)`：用“褶皱颜色”，并且是**半透明**的（`100` 的透明度）。
                # `False`：`False` 告诉 `aalines`：“不要把线的‘终点’和‘起点’连起来”。（我们画的是一条线，不是一个闭合的多边形）。
                # `pleat_points`：使用我们刚刚算好的那一长串点来画线。

        fin_ratio = 0.3
        #定义一个“比例”变量。`0.3` 意思是，我们想把背鳍放在鲸鱼背部 30% 的位置（从头开始算）。
        fin_angle = fin_ratio * math.pi
        #把 `0.3` (30%) 这个“比例”，转换成 `sin` 和 `cos` 能看懂的“角度”（弧度）。
        fin_base_x = center_x + (width / 2) * math.cos(fin_angle)
        # 计算背鳍的“基座 X 坐标”。
        # 它在用 `cos` 找到鲸鱼身体曲线上，角度等于 `fin_angle` 的那个点的“X 坐标”。
        fin_base_y = center_y - (height / 2.0) * math.sin(fin_angle) ** 0.8
        # 计算背鳍的“基座 Y 坐标”。
        # 注意：这个公式 `center_y - (height / 2.0) * ...` 和我们之前画“鲸鱼背部” (`dy_top`) 的公式**一模一样**！
        # 确保这个基座 `(fin_base_x, fin_base_y)` 100% 精确地“贴”在鲸鱼的背部曲线上。
        dorsal_fin_points = [(fin_base_x + 5, fin_base_y), (fin_base_x - 5, fin_base_y - 12),
                             (fin_base_x - 15, fin_base_y)]
        # 定义一个“小三角形”的三个顶点坐标，来作为背鳍的形状。
        # 所有的坐标都是“相对于”我们刚算好的“基座” `(fin_base_x, fin_base_y)` 来计算的：
        # 1. `(基座X + 5, 基座Y)`：三角形的“右下角”。
        # 2. `(基座X - 5, 基座Y - 12)`：三角形的“顶点”（向上 12 像素）。
        # 3. `(基座X - 15, 基座Y)`：三角形的“左下角”。
        pygame.draw.polygon(surface, self.color_body, dorsal_fin_points)
        # 这是“画多边形”的命令。
        # `surface`：画在“鲸鱼画布”上。
        # `self.color_body`：用和鲸鱼身体“一样的颜色”，这样鳍和身体就融为一体了。
        # `dorsal_fin_points`：画我们刚刚定义的那个“小三角形”。
        # 结果：一只带背鳍的鲸鱼就画好了！

    # <--- 【新增代码 4/5】 --->
    # 新增方法：计算眼睛在屏幕上的绝对位置和碰撞区域
    def calculate_eye_screen_pos_and_rect(self):
        # --- 这部分代码是从 draw() 方法中复制并修改而来 ---
        # 1. 计算当前帧画布中心和身体起伏
        canvas_center = pygame.Vector2(self.whale_surface.get_width() / 2, self.whale_surface.get_height() / 2)
        body_undulation = math.sin(self.time * 0.08) * 6
        animated_canvas_center = canvas_center + pygame.Vector2(0, body_undulation)

        # 2. 计算身体矩形在画布上的位置
        body_rect = pygame.Rect(0, 0, self.width, self.height)
        body_rect.center = animated_canvas_center

        # 3. 计算眼睛相对于画布(0,0)的坐标 (和 draw 方法一致)
        eye_pos_on_surface = (body_rect.right - 55, body_rect.centery - 18)
        eye_pos_vec_on_surface = pygame.Vector2(eye_pos_on_surface)  # 转换为向量

        # 4. 计算眼睛相对于画布中心点的向量
        vec_center_to_eye = eye_pos_vec_on_surface - canvas_center

        # 5. 旋转这个向量
        final_angle = -45  # 鲸鱼的旋转角度
        # Pygame 的 rotate 是逆时针旋转，所以要用 -final_angle 来实现顺时针
        rotated_vec = vec_center_to_eye.rotate(-final_angle)

        # 6. 计算眼睛在屏幕上的绝对中心坐标
        # 鲸鱼在屏幕上的中心 (self.rect.center) + 旋转后的眼睛向量
        absolute_eye_pos = self.rect.center + rotated_vec

        # 7. 创建一个以眼睛为中心的小矩形作为碰撞区域 (例如 10x10)
        eye_collision_rect = pygame.Rect(0, 0, 10, 10)
        eye_collision_rect.center = (int(absolute_eye_pos.x), int(absolute_eye_pos.y))

        # 8. 将计算好的碰撞区域存储起来，方便主循环获取
        self.eye_collision_rect = eye_collision_rect

    # 新增方法：触发眼睛闭合
    def trigger_eye_close(self, duration_seconds=5):
        if not self.eye_closed:  # 只有在眼睛睁开时才能触发
            self.eye_closed = True
            # 将秒转换为帧数 (假设游戏帧率是 60 FPS)
            self.eye_closed_timer = duration_seconds * 60
    # <--- 【新增代码 4/5】结束 --->


    # 绘制方法，每一帧调用
    def draw(self, screen):
        # 绘制环绕鲸鱼的泡泡
        whale_center = pygame.Vector2(self.rect.center)
        """
        self.rect：是一个 pygame.Rect（矩形）对象
        .center：是这个 Rect 对象的一个特殊属性。它的作用是一次性提供这个矩形“中心点的 (x, y) 坐标”
        self.rect.center 不是一个 变量，它本身就是一个包含了两个变量 (x, y) 的“元组”(Tuple)
        2.whale_center是一个 pygame.Vector2 对象。,可以让你用 .x 和 .y 来访问数据,例如：
        whale_center.x (就是 400)     whale_center.y (就是 300)
        """
        for bubble in self.bubbles:
            pos_x = whale_center.x + bubble['orbit_radius'] * math.cos(bubble['angle'])
            pos_y = whale_center.y + bubble['orbit_radius'] * math.sin(bubble['angle'])
            # 它的意思是：从鲸鱼的“中心点”出发，根据泡泡自己的“环绕半径”和“当前角度”，
            # 计算出这个泡泡现在应该在 (x, y) 哪个位置。
            alpha = 100 + math.sin(bubble['alpha_phase']) * 50
            # `math.sin(...)` 在 -1 到 1 之间摆动。
            # `... * 50` 把它变成 -50 到 50。
            # `100 + ...` 把它变成 50 到 150。
            # 目的：让每个泡泡的“透明度”在 50 到 150 之间“呼吸”或“闪烁”，增加动感。
            pygame.draw.circle(screen, (*bubble['color'], alpha), (int(pos_x), int(pos_y)), bubble['radius'])
            # `screen`：直接画在“主屏幕”上（这些泡泡是“环绕”鲸鱼的，不是鲸鱼身体的一部分）。
            # `(*bubble['color'], alpha)`：这是我们学过的“打包”技巧。把泡泡的“RGB颜色”和刚刚算出来的“透明度”打包成一个 (R, G, B, A) 颜色。
            # `(int(pos_x), int(pos_y))`：把算好的小数坐标“取整”，作为圆心。

        ## --- 修改点 3：绘制鲸鱼后方追踪泡泡 --- ##
        # 绘制追踪泡泡
        for bubble in self.trail_bubbles:
            #遍历“拖尾泡泡”列表里的每一个泡泡
            if bubble['alpha'] > 0 and bubble['radius'] > 0:
                # 解释：这是一个“优化”检查。
                # 如果这个泡泡的“生命周期”已经结束（透明度 `alpha` 降到了 0），
                # 或者它已经缩得太小（`radius` 变成了 0），那就“没必要画了”，直接跳过。
                pygame.draw.circle(screen, (*bubble['color'], int(bubble['alpha'])),
                                   (int(bubble['pos'].x), int(bubble['pos'].y)), int(bubble['radius']))
                # 解释：画出这个拖尾泡泡。
                # 这次更简单，直接用泡泡自己字典里存的 `pos` 和 `alpha` 来画。
        # --- 绘制鲸鱼本身 ---
        # 1. 清空临时画布
        self.whale_surface.fill((0, 0, 0, 0))
        # 解释：这是“擦黑板”！
        # `self.whale_surface` 是鲸鱼的“专属画板”。
        # `fill((0, 0, 0, 0))` 的意思是“用一个颜色填满整个画板”。
        # `(0, 0, 0)` 是黑色，但最后一个 `0` 是“透明度”(Alpha)！
        # 意思是“用 100% 的透明色”来填满画板，也就是“把画板擦干净”！
        # 为什么必须这么做？ 因为 `draw` 函数每一帧都会被调用。如果不擦掉上一帧的画，这只鲸鱼就会留下无数的“残影”。
        canvas_center = pygame.Vector2(self.whale_surface.get_width() / 2, self.whale_surface.get_height() / 2)
        # 获取这块“临时画布”的“本地中心点”，作为我们画身体的“基准点”。
        # 2. 定义动画参数，全部由 self.time 驱动
        tail_sway = math.sin(self.time * 0.12) * 25     # 尾巴摆动幅度
        # 用 `sin` 计算一个 -25 到 +25 的值，模拟“尾巴左右摆动”。
        fin_flap = math.sin(self.time * 0.25) * 8       # 鳍扇动幅度
        # 用 `sin` 计算一个 -8 到 +8 的值，模拟“胸鳍上下扇动”。
        body_undulation = math.sin(self.time * 0.08) * 6    # 身体起伏幅度
        # 用 `sin` 计算一个 -6 到 +6 的值，模拟“整个身体在水中上下起伏”。

        animated_canvas_center = canvas_center + pygame.Vector2(0, body_undulation)
        # 应用“身体起伏”动画
        # 把“画布中心点”和“身体起伏的Y偏移量”相加，得到一个“会动的中心点”。
        body_rect = pygame.Rect(0, 0, self.width, self.height)
        body_rect.center = animated_canvas_center
        # 创建鲸鱼“身体”的矩形，并把它的中心对齐到“会动的中心点”。
        # 画尾鳍 (Tail Fin)
        tail_base_x = body_rect.left + 30
        """
        1.tail_base_x:这是一个变量，它的名字意思是“尾巴基座的 X 坐标”
        2.body_rect.left:这是鲸鱼身体矩形最左边的 X 坐标
        3.+ 30:意思是“在最左边的坐标基础上，加上 30”。 在 Pygame 的坐标系中，X 坐标增加意味着“向右移动”
    总结：“尾鳍的‘根部’X 坐标，不是在鲸鱼的身体外面，而是在从尾巴尖 (.left) 向身体内部（向右）移动 30 个像素的位置。”
        """
        tail_base_y = body_rect.centery
        """
        tail_base_y: 这是尾巴基座的 Y 坐标
        body_rect.centery: 这是鲸鱼身体矩形在垂直方向的正中心的 Y 坐标
    总结：这行代码的意思是：“尾鳍的‘根部’Y 坐标，就定在鲸鱼身体的‘腰线’（垂直正中心）上
        """
        # 找到“尾巴根部”的坐标（身体左侧 `left` 往里 30 像素的地方）。
        fin_color = self.color_body
        fin_left = [(tail_base_x, tail_base_y), (tail_base_x - 60, tail_base_y - 30 + tail_sway),
                    (tail_base_x - 55, tail_base_y)]
        """
        fin_left (左尾鳍) 的第二个点： (tail_base_x - 60, tail_base_y - 30 + tail_sway) (Y 坐标是 ... - 30 ...，意思是向上 30 像素
        1.尾鳍是由元组组成的列表，因为要画一个形状（在这里是一个三角形），你必须提供这个形状所有“顶点”的坐标。
        一个三角形有三个顶点，所以列表里就需要有三个元组 (x, y)。
        """
        # 定义“左尾鳍”的三个点（一个三角形）。
        # 注意：`tail_base_y - 30 + tail_sway`！
        # 这就是“应用动画”！它把第 4 步算好的 `tail_sway` 值加到了尾鳍尖端的 Y 坐标上，让它动了起来。
        pygame.draw.polygon(self.whale_surface, fin_color, fin_left)
        """
        1.用polyon画出来的是封闭图形，会按照顺序连线，从第一个点画一条线到第二个点。再从第二个点画一条线到第三个点。
        最后，因为它画的是 polygon（多边形），它会自动从第三个点画一条线回到第一个点，把这个形状封闭起来。
        封闭之后，它会用你指定的颜色把这个形状填满
        """
        fin_right = [(tail_base_x, tail_base_y), (tail_base_x - 60, tail_base_y + 30 + tail_sway),
                     (tail_base_x - 55, tail_base_y)]
        """
        fin_right (右尾鳍) 的第二个点： (tail_base_x - 60, tail_base_y + 30 + tail_sway) (Y 坐标是 ... + 30 ...，意思是向下 30 像素)
        """
        """
        作者在用两个三角形来拼出一个完整的、分岔的鲸鱼尾巴,两个列表 fin_left 和 fin_right 的名字起得可能有点让人误会，
        它们不是指鲸鱼身体的“左边”和“右边”，而是指尾鳍的“上半部分”和“下半部分”。
        """
        pygame.draw.polygon(self.whale_surface, fin_color, fin_right)

        self._draw_whale_body(self.whale_surface, body_rect, self.time)
        #函数的调用
        fin_points = [(body_rect.centerx - 5, body_rect.centery + 10),
                      (body_rect.centerx + 30, body_rect.centery + 35 + fin_flap),
                      (body_rect.centerx + 25, body_rect.centery + 10)]
        """
        点1 (centerx - 5, ...): 鳍的“根部”点（靠近身体）。
        点2 (centerx + 30, ...): 鳍的**“尖端”，也是“会动”**的部分！
        ... + fin_flap：这就是动画！ fin_flap 是你之前用 sin 函数算出来的一个在 -8 和 +8 之间摆动的值。把它加在这里，就让这个“尖端”可以上下扇动！
        点3 (centerx + 25, ...): 鳍的“后缘”点，用来构成三角形的第三个角。
        """
        pygame.draw.polygon(self.whale_surface, self.color_body, fin_points)
        """
        它在 self.whale_surface（临时画布）上，
        用 self.color_body（身体一样的颜色），把 fin_points（我们刚定义的那个“会动的三角形”）画了出来，并填满了颜色。
        结果： 鲸鱼的身体上有了一个会上下扇动的胸鳍
        """

        eye_pos = (body_rect.right - 55, body_rect.centery - 18)
        """
        它在计算眼睛应该在身体的哪个固定位置。（在身体矩形“右边”(right) 往回 55 像素，“中心”(centery) 往上 18 像素的地方）。
        """

        # <--- 【修改代码 5/5】 --->
        # 根据 self.eye_closed 状态决定画什么
        if self.eye_closed:
            # 如果是闭合状态，画一条横线
            pygame.draw.line(self.whale_surface, (0, 0, 0), (eye_pos[0] - 4, eye_pos[1]), (eye_pos[0] + 4, eye_pos[1]),
                             2)
        elif self.time % 120 < 8:  # 保留原有的眨眼动画逻辑
            # 短暂闭眼（眨眼）
            pygame.draw.line(self.whale_surface, (0, 0, 0), (eye_pos[0] - 4, eye_pos[1]), (eye_pos[0] + 4, eye_pos[1]),
                             2)
        else:
            # 正常睁开，画圆圈
            pygame.draw.circle(self.whale_surface, (0, 0, 0), eye_pos, 4)
        # <--- 【修改代码 5/5】结束 --->

        #将整个画好的鲸鱼画布进行旋转
        final_angle = -45
        """
        设置变量,定义了我们希望鲸鱼最终倾斜的角度。
        在 Pygame 中，-45 度代表顺时针旋转 45 度。所以这只鲸鱼看起来会是头朝右下方（↘），像在下潜一样。
        """
        rotated_whale_surf = pygame.transform.rotate(self.whale_surface, final_angle)
        """
        1.pygame.transform.rotate:这是 Pygame “变换工具箱”里的“旋转”工具。
        self.whale_surface:这是“被旋转”的物体。也就是我们之前在上面画好了身体、尾巴、鳍、眼睛的“临时画布”。此时，这张画布上的鲸鱼还是“水平”的。
        2.旋转不会修改原始的 self.whale_surface。
        它会创建一块全新的、更大的画布（rotated_whale_surf），这块新画布上装着“已经被旋转好的鲸鱼”。
        """
        # 8. 获取旋转后的矩形，并将其中心与鲸鱼的逻辑中心对齐
        rotated_rect = rotated_whale_surf.get_rect(center=self.rect.center)
        """
        1.rotated_whale_surf.get_rect():
        “请获取这张旋转后的画布的新矩形。”这个新矩形知道鲸鱼旋转后的新尺寸",但它的位置默认是 (0, 0)
        2.self.rect.center：这是鲸鱼“逻辑上”在主屏幕的位置（它在 update 函数里被更新，代表“鲸鱼现在应该在屏幕的哪个中心点”）。
        center=...：这是我们学过的“关键字参数”技巧。
        它告诉 get_rect：“别把新矩形放在 (0,0)！请立刻把这个新矩形的中心点，移动到我指定的 self.rect.center（逻辑中心点）这个坐标上！”
        3.rotated_rect = ...:
        这个变量现在存着一个“完美的”矩形：它既有旋转后的正确尺寸，又有在屏幕上的正确中心位置。
        """
        # 9. 将最终的、旋转好的鲸鱼图像画到主屏幕上
        screen.blit(rotated_whale_surf, rotated_rect)
        """
        screen.blit:这是“粘贴”命令，把一张画布“贴”到另一张上。
        rotated_whale_surf:贴什么？ 贴我们那张“已经旋转好的”鲸鱼画布。
        rotated_rect:贴在哪里？ 贴在我们上一步刚刚算好的那个“位置完美”的矩形上。
        """

        # 将姓名和学号绘制在鲸鱼附近
        name_pos = (self.rect.centerx - 50, self.rect.centery - 50)
        id_pos = (self.rect.centerx - 50, self.rect.centery - 30)
        screen.blit(self.name_surf, self.name_surf.get_rect(center=name_pos))
        screen.blit(self.id_surf, self.id_surf.get_rect(center=id_pos))

        # --- 在绘制结束后，计算下一帧的眼睛位置和碰撞区域 ---
        # 我们把这个计算放在 draw 的最后，因为它依赖很多 draw 过程中计算出的中间变量
        self.calculate_eye_screen_pos_and_rect()